/*
 * Copyright 2020 eBlocker Open Source UG (haftungsbeschraenkt)
 *
 * Licensed under the EUPL, Version 1.2 or - as soon they will be
 * approved by the European Commission - subsequent versions of the EUPL
 * (the "License"); You may not use this work except in compliance with
 * the License. You may obtain a copy of the License at:
 *
 *   https://joinup.ec.europa.eu/page/eupl-text-11-12
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */
package org.eblocker.lists.malware;

import org.eblocker.server.common.malware.MalwareEntry;
import org.eblocker.lists.util.HttpClient;
import com.google.common.io.ByteStreams;
import org.apache.commons.io.Charsets;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.xml.bind.DatatypeConverter;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;

public class MalwarePatrolDownloader {

    private static final Logger log = LoggerFactory.getLogger(MalwarePatrolDownloader.class);

    private static final String USERNAME_KEY = "provider.malwarepatrol.auth.username";
    private static final String PASSWORD_KEY = "provider.malwarepatrol.auth.password"; //NOSONAR: this is no password but a key
    private static final String MALWARE_SANITIZED_URL = "provider.malwarepatrol.malware.sanitized.url";
    private static final String MALWARE_SANITIZED_MD5_URL = "provider.malwarepatrol.malware.sanitized.md5.url";
    private static final String MALWARE_SANITIZED_SHA1_URL = "provider.malwarepatrol.malware.sanitized.sha1.url";

    private final HttpClient httpClient;
    private final MalwarePatrolSanitizedUrlsParser parser;

    private final String username;
    private final String password;
    private final String malwareSanitizedUrl;
    private final String malwareSanitizedMd5Url;
    private final String malwareSanitizedSha1Url;

    public MalwarePatrolDownloader(HttpClient httpClient, MalwarePatrolSanitizedUrlsParser parser, Properties properties) {
        this.httpClient = httpClient;
        this.parser = parser;

        username = properties.getProperty(USERNAME_KEY);
        password = properties.getProperty(PASSWORD_KEY);
        malwareSanitizedUrl = properties.getProperty(MALWARE_SANITIZED_URL);
        malwareSanitizedMd5Url = properties.getProperty(MALWARE_SANITIZED_MD5_URL);
        malwareSanitizedSha1Url = properties.getProperty(MALWARE_SANITIZED_SHA1_URL);
    }

    public List<MalwareEntry> retrieveEntries() throws IOException, MalwareListException {
        return parse(download());
    }

    private InputStream download() throws IOException, MalwareListException {
        byte[] content = downloadList();
        checkChecksum(content, "MD5", malwareSanitizedMd5Url);
        checkChecksum(content, "SHA1", malwareSanitizedSha1Url);
        return new ByteArrayInputStream(content);
    }

    private byte[] downloadList() throws IOException {
        InputStream content = download(malwareSanitizedUrl);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ByteStreams.copy(content, baos);
        return baos.toByteArray();
    }

    private InputStream download(String url) throws IOException {
        log.debug("downloading {} ", url);
        return httpClient.download(url, username, password);
    }

    private void checkChecksum(byte[] message, String algorithm, String url) throws IOException, MalwareListException {
        byte[] expectedChecksum = downloadChecksum(url);
        byte[] actualChecksum = getDigest(algorithm).digest(message);
        if (!Arrays.equals(expectedChecksum, actualChecksum)) {
            log.debug("{} {} does not match {} !", algorithm, DatatypeConverter.printHexBinary(expectedChecksum), DatatypeConverter.printHexBinary(actualChecksum));
            throw new ChecksumException(algorithm + " checksum mismatch! Expected " + expectedChecksum + " but got " + actualChecksum);
        }
        log.debug("{} {} ok", algorithm, DatatypeConverter.printHexBinary(expectedChecksum));
    }

    private byte[] downloadChecksum(String url) throws IOException {
        try (InputStream inputStream = download(url)) {
            String checksum = IOUtils.toString(inputStream, Charsets.UTF_8).trim();
            return DatatypeConverter.parseHexBinary(checksum);
        }
    }

    private MessageDigest getDigest(String algorithm) {
        try {
            return MessageDigest.getInstance(algorithm);
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalArgumentException("unsupported message digest " + algorithm, e);
        }
    }

    private List<MalwareEntry> parse(InputStream inputStream) throws IOException {
        log.debug("parsing entries");
        return parser.parse(inputStream);
    }

    public static class ChecksumException extends MalwareListException {
        ChecksumException(String message) {
            super(message);
        }
    }
}
